<template>
  <view class="s-picker-wrap">
    <s-popup
      :height="height"
      :z-index="zIndex"
      :value="visible"
      :position="position"
      :animate="animate"
      :animate-duration="animateDuration"
      :mask="mask"
      :mask-opacity="maskOpacity"
      :mask-close="maskClose"
      @open="onOpen"
      @closed="onClosed"
    >
      <view class="s-picker" :class="custom_class" :style="custom_style">
        <view class="s-picker__header s-hairline--bottom">
          <view class="s-picker__header-left">
            <s-button
              v-if="showCancel"
              type="text"
              size="custom"
              square
              custom-class="s-picker__action s-picker__cancel-action"
              :custom-style="{ color: cancelColor }"
              @click="onCancel"
            >
              {{ cancelText }}
            </s-button>
          </view>
          <view class="s-picker__title" :style="{ color: titleColor }">{{ title }}</view>
          <view class="s-picker__header-right">
            <s-button
              type="text"
              size="custom"
              square
              :custom-class="
                $mergeClass('s-picker__action s-picker__confirm-action', {
                  's-picker__confirm-action--disabled': isDisabledConfirm,
                })
              "
              :custom-style="{ color: isDisabledConfirm ? isDisabledConfirm : confirmColor }"
              @click="onConfirm"
            >
              {{ confirmText }}
            </s-button>
          </view>
        </view>
        <view class="s-picker__body">
          <picker-view
            v-if="showPickerView"
            :value="indexList"
            class="s-picker__picker-view"
            :indicator-class="indicator_class"
            :indicator-style="indicator_style"
            :mask-class="mask_class"
            :mask-style="mask_Style"
            @change="onPickerChange"
            @pickstart="onPickerStart"
            @pickend="onPickerEnd"
          >
            <picker-view-column v-for="(column, columnIndex) of columnDataList" :key="columnIndex">
              <view v-for="(item, index) of column" :key="index" class="s-picker__option">
                <slot :scopeParams="scopeParams" :i="columnIndex" :item="item" :index="index">
                  <view class="s-picker__option-text">{{ item[labelKey] }}</view>
                </slot>
              </view>
            </picker-view-column>
          </picker-view>
        </view>
        <view v-if="safeAreaInsetBottom && position === 'bottom'" style="height: var(--safe-area-inset-bottom)"></view>
      </view>
    </s-popup>
  </view>
</template>

<script>
import componentMixin from '../../mixins/componentMixin';
import toValidListData from '../../utils/toValidListData';
import getComponentOptions from '../../common/getComponentOptions';

/**
 * s-picker 底部弹出滑动选择组件 picker
 * @description 滑动选则，支持多列，级联选择
 * @property {Number|String} height 高度
 * @property {Boolean} visible 弹框显示隐藏
 * @property {Array|Object} columns 选择数据列表 [{label:'中国',value:'1'}]，multiple 为true时，必须是数组格式[[{label:'中国',value:'1'}]]
 * @property {*} value 选中的value数组
 * @property {Boolean} multiple 是否是多列选择
 * @property {String} valuelKey 取数据中的那个字段作为value关联
 * @property {String} labelKey 取数据中的那个字段作为显示文字
 * @property {String|Array} placeholder 给列数据的前面插入一项value为''的占位选择项数据
 * @property {String} text 双向绑定用来同步确认后的选中label描述
 * @property {String} separator text在多列选择结果字符串的分割符号，默认 -
 * @property {String} title 标题
 * @property {String} cancelText 取消按钮文字
 * @property {String} confirmText 确认按钮文字
 * @property {String} titleColor 标题颜色
 * @property {String} cancelColor 取消按钮颜色
 * @property {String} confirmColor 确认按钮颜色
 * @property {String} disabledColor 确认按钮禁用时颜色
 * @property {String|Object} indicatorClass 设置选择器中间选中框的类名
 * @property {String|Object} indicatorStyle 设置选择器中间选中框的样式
 * @property {String|Object} maskClass 设置蒙层的类名
 * @property {String|Object} maskStyle 设置蒙层的样式
 * @property {String} position 显示位置，同popup组件
 * @property {String|Number} zIndex 同popup组件position属性
 * @property {String|Object} animate 过渡效果，同popup组件animate属性
 * @property {Number|String|Object} animateDuration 显示关闭过渡效果时间，同popup组件animateDuration属性
 * @property {Boolean} value 双向绑定picker显示隐藏 value.sync
 * @property {Boolean} mask 是否显示遮罩，同popup组件mask属性
 * @property {Number|String} maskOpacity 遮罩透明度 0.7，同popup组件maskOpacity属性
 * @property {Boolean} showCancel 是否显示取消按钮
 * @property {Boolean} maskClose 点击遮罩是否关闭
 * @property {Boolean} safeAreaInsetBottom 是否开启底部安全区域适配
 * @event {Function} change ({
    index: 当前列下标,
    index: 当前列选中的下标,
    value: 当前列选中的value,
    data: 当前列选中的数据对象,
    indexList: 列选中的下标数组,
    valueList: 列选中的value数组,
    dataList: 列选中的数据对象数组,
    text: 列选中的label拼接字符串,
    setData: 级联选择时在change事件中设置列的数据,
    getText: 根据自定义间隔符返回text,
  }) 滑动改变时触发
 * @event {Function} confirm ({
    indexList: 列选中的下标数组,
    valueList: 列选中的value数组,
    dataList: 列选中的数据对象数组,
    text: 列选中的label拼接字符串,
    getText: 根据自定义间隔符返回text,
  }) 点击确认时触发
 * @example <s-picker v-model="showPicker" :columns="columns" :selected-value.sync="value" :selected-text.sync="text" />
 */
export default {
  name: 'SPicker',
  mixins: [componentMixin],
  options: getComponentOptions({ virtualHost: false }),
  props: {
    height: [Number, String],
    visible: Boolean,
    columns: {
      type: [Array, Object],
      default: () => [],
    },
    value: {
      default: '',
    },
    multiple: Boolean,
    valueKey: {
      type: String,
      default: 'value',
    },
    labelKey: {
      type: String,
      default: 'label',
    },
    placeholder: {
      type: [Array, String],
      default: () => [],
    },
    text: String,
    separator: {
      type: String,
      default: '-',
    },
    title: String,
    titleColor: String,
    cancelText: {
      type: String,
      default: '取消',
    },
    cancelColor: String,
    confirmText: {
      type: String,
      default: '确定',
    },
    confirmColor: String,
    disabledColor: String,
    indicatorClass: [String, Object],
    indicatorStyle: [String, Object],
    maskClass: [String, Object],
    maskStyle: [String, Object],
    position: {
      type: String,
      default: 'bottom',
    },
    zIndex: [Number, String],
    animate: {
      type: [String, Boolean, Object],
      default: 'auto',
    },
    animateDuration: {
      type: [Number, String, Object],
      default: 300,
    },
    mask: {
      type: Boolean,
      default: true,
    },
    maskOpacity: {
      type: [Number, String],
      default: 0.7,
    },
    showCancel: {
      type: Boolean,
      default: true,
    },
    maskClose: {
      type: Boolean,
      default: true,
    },
    safeAreaInsetBottom: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      columnDataList: [], // column 合法数据数组
      indexList: [], // column 每列选中的索引
      showPickerView: false, // 初始化picker-view
      isDisabledConfirm: false, // 滑动时禁用确认按钮
    };
  },
  computed: {
    indicator_class() {
      return this.$mergeClass('s-picker__indicator', this.indicatorClass);
    },
    indicator_style() {
      return this.$mergeStyle(this.indicatorStyle);
    },
    mask_class() {
      return this.$mergeClass('s-picker__mask', this.maskClass);
    },
    mask_Style() {
      return this.$mergeStyle(this.maskStyle);
    },
    initState() {
      return [this.columns, this.multiple];
    },
  },
  watch: {
    initState() {
      this.init();
    },
    value(value) {
      this.setValueList(value);
    },
  },
  created() {
    Object.assign(this, {
      columnIndexList: [], // [0,1,2] column选中的下标数组
      valueList: [], // column 每列选中的value
      dataList: [], // column 每列选中的数据对象
    });
    this.init();
  },
  methods: {
    init() {
      const columnIndexList = [];
      const columnDataList = [];
      const valueList = [];
      const indexList = [];
      const dataList = [];
      const columns = this.multiple ? this.columns : [this.columns];
      const values = this.$getType(this.value) === 'array' ? this.value : [this.value];
      columns.forEach((data, i) => {
        columnIndexList[i] = i;
        columnDataList[i] = this.getValidData(i, data);
        indexList[i] = 0;
        valueList[i] = typeof values[i] !== 'undefined' ? values[i] : '';
        dataList[i] = null;
      });
      this.columnIndexList = columnIndexList;
      this.columnDataList = columnDataList;
      this.valueList = valueList;
      this.indexList = indexList;
      this.dataList = dataList;
      this.setValueList(this.value);
    },
    onOpen() {
      this.$nextTick(() => {
        this.setValueList(this.value);
        this.showPickerView = true;
        this.isDisabledConfirm = false;
      });
    },
    onClosed() {
      this.showPickerView = false;
      this.hide();
    },
    hide() {
      this.$emit('update:visible', false);
    },
    onPickerStart() {
      this.isDisabledConfirm = true;
    },
    onPickerEnd() {
      this.isDisabledConfirm = false;
    },
    onPickerChange(e) {
      const prevSelectedIndexList = this.indexList;
      this.indexList = [...e.detail.value];
      this.indexList.some((index, i) => {
        if (index !== prevSelectedIndexList[i]) {
          this.onChange(i);
          return true;
        }
      });
    },
    getValidData(i, data) {
      const valueKey = this.valueKey;
      const labelKey = this.labelKey;
      const placeholderList = this.$getType(this.placeholder) === 'array' ? this.placeholder : [this.placeholder];
      const placeholder = placeholderList[i];
      return [
        ...(placeholder ? [{ [valueKey]: '', [labelKey]: placeholder }] : []),
        ...toValidListData(data, valueKey, labelKey),
      ];
    },
    setData(i, data) {
      data = this.getValidData(i, data);
      const beforeData = this.columnDataList[i];
      if (!beforeData || JSON.stringify(beforeData) !== JSON.stringify(data)) {
        this.$set(this.columnDataList, i, data);
        this.scrollToColumnIndex(i);
      }
    },
    scrollToColumnIndex(i) {
      const value = this.valueList[i];
      const valueIndex = this.columnDataList[i].findIndex(item => String(item[this.valueKey]) === String(value));
      let index = this.getIndex(i);
      index = valueIndex > -1 ? valueIndex : index;
      this.$set(this.indexList, i, index);
      this.onChange(i);
    },
    onChange(i) {
      const index = this.getIndex(i);
      const data = this.columnDataList[i][index];
      if (data !== this.dataList[i]) {
        this.dataList[i] = data;
        this.valueList[i] = data ? data[this.valueKey] : '';
        this.$emit('change', {
          i,
          index,
          value: this.valueList[i],
          text: this.getText(),
          data,
          indexList: [...this.indexList],
          valueList: [...this.valueList],
          dataList: [...this.dataList],
          setData: this.setData,
          getText: this.getText,
        });
      }
    },
    getIndex(i) {
      return Math.max(0, Math.min(this.indexList[i], this.columnDataList[i].length - 1));
    },
    getText(separator) {
      if (typeof separator === 'undefined') separator = this.separator;
      return this.dataList.filter(item => item && item[this.labelKey] !== '').map(item => item[this.labelKey]).join(separator);
    },
    setValueList(value) {
      const values = this.$getType(value) === 'array' ? value : [value];
      if (values.length) {
        this.columnIndexList.forEach(i => {
          this.valueList[i] = typeof values[i] !== 'undefined' ? values[i] : '';
        });
      }
      if (this.visible) {
        this.columnIndexList.forEach(i => {
          this.scrollToColumnIndex(i);
        });
      }
    },
    onCancel() {
      this.hide();
      this.$emit('cancel');
    },
    onConfirm() {
      if (!this.isDisabledConfirm) {
        const text = this.getText();
        this.$emit('input', this.multiple ? [...this.valueList] : this.valueList[0]);
        this.$emit('update:text', text);
        const params = {
          text,
          indexList: [...this.indexList],
          valueList: [...this.valueList],
          dataList: [...this.dataList],
          getText: this.getText,
        };
        if (!this.multiple) {
          params.value = this.valueList[0];
          params.value = this.dataList[0];
        }
        this.$emit('confirm', params);
        this.hide();
      }
    },
  },
};
</script>

<style lang="scss" src="./index.scss"></style>
